]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
Import part of the notification area applet, and use it to add a
authorDan Winship <danw@src.gnome.org>
Fri, 14 Nov 2008 17:21:56 +0000 (17:21 +0000)
committerDan Winship <danw@src.gnome.org>
Fri, 14 Nov 2008 17:21:56 +0000 (17:21 +0000)
notification are to the panel. A bit warty, but we don't know how we want
the final UI to look anyway. (The fact that transparency doesn't work is
a known bug.)

svn path=/trunk/; revision=44

12 files changed:
configure.ac
js/ui/panel.js
src/Makefile-tidy.am
src/Makefile-tray.am [new file with mode: 0644]
src/Makefile.am
src/shell-tray-manager.c [new file with mode: 0644]
src/shell-tray-manager.h [new file with mode: 0644]
src/tray/na-marshal.list [new file with mode: 0644]
src/tray/na-tray-child.c [new file with mode: 0644]
src/tray/na-tray-child.h [new file with mode: 0644]
src/tray/na-tray-manager.c [new file with mode: 0644]
src/tray/na-tray-manager.h [new file with mode: 0644]

index fdee6f45c3d7f2f3ccb7fec539ad47cba38fef40..f68801c2d879e96599e2843a39e0953659b41f5f 100644 (file)
@@ -17,6 +17,10 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
                    [The prefix for our gettext translation domains.])
 
 PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 metacity-plugins gjs-gi-1.0)
+PKG_CHECK_MODULES(TRAY, gtk+-2.0)
+
+# Sets GLIB_GENMARSHAL and GLIB_MKENUMS
+AM_PATH_GLIB_2_0()
 
 changequote(,)dnl
 if test "x$GCC" = "xyes"; then
index 3216746d0dcbf01b92f8f74f47fbb9f87f0a0a91..a9f3c6973d9df39d03417800bab585739520170e 100644 (file)
@@ -4,10 +4,12 @@ const Mainloop = imports.mainloop;
 
 const Shell = imports.gi.Shell;
 const Clutter = imports.gi.Clutter;
+const Tidy = imports.gi.Tidy;
 
 const Main = imports.ui.main;
 
 const PANEL_HEIGHT = 32;
+const TRAY_HEIGHT = 24;
 const PANEL_BACKGROUND_COLOR = new Clutter.Color();
 PANEL_BACKGROUND_COLOR.from_pixel(0xeeddccff);
 
@@ -26,7 +28,7 @@ Panel.prototype = {
                                                 width: global.screen_width+2,
                                                 height: PANEL_HEIGHT+1,
                                                 border_width: 1});
-       background.set_position(-1, -1)
+       background.set_position(-1, -1);
        this._group.add_actor(background);
 
        let message = new Clutter.Label({ font_name: "Sans Bold 16px",
@@ -35,11 +37,35 @@ Panel.prototype = {
        message.set_position(5, 5);
        this._group.add_actor(message);
 
+       this._grid = new Tidy.Grid({ height: TRAY_HEIGHT,
+                                    valign: 0.5,
+                                    end_align: true,
+                                    column_gap: 2 })
+       this._group.add_actor(this._grid);
+
        this._clock = new Clutter.Label({ font_name: "Sans Bold 16px",
                                          text: "" });
-       this._clock.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
-       this._clock.set_position(global.screen_width - 5, 5);
-       this._group.add_actor(this._clock);
+       this._grid.add_actor(this._clock);
+
+       this._grid.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
+       this._grid.set_position(global.screen_width - 2, (PANEL_HEIGHT - TRAY_HEIGHT) / 2);
+
+       this._traymanager = new Shell.TrayManager();
+       let panel = this;
+       this._traymanager.connect('tray-icon-added',
+           function(o, icon) {
+               panel._grid.add_actor(icon);
+               /* bump the clock back to the end */
+               panel._grid.remove_actor(panel._clock);
+               panel._grid.add_actor(panel._clock);
+               panel._grid.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
+           });
+       this._traymanager.connect('tray-icon-removed',
+           function(o, icon) {
+               panel._grid.remove_actor(icon);
+               panel._grid.move_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
+           });
+       this._traymanager.manage_stage(global.stage);
 
        message.connect('button-press-event',
            function(o, event) {
@@ -68,8 +94,6 @@ Panel.prototype = {
 
     _updateClock: function() {
        this._clock.set_text(new Date().toLocaleFormat("%H:%M"));
-       this._clock.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
-
        return true;
     }
 };
index c82e74b16d7e0aa10f55c48f37f4cdcccf25785e..91c9393d5ca9838b34168ea0aeeca3fd6bdd029b 100644 (file)
@@ -1,8 +1,5 @@
 NULL =
 
-GLIB_GENMARSHAL = `pkg-config --variable=glib_genmarshal glib-2.0`
-GLIB_MKENUMS = `pkg-config --variable=glib_mkenums glib-2.0`
-
 tidy_cflags =                                  \
        -I$(top_srcdir)/src                     \
        -DPREFIX=\""$(prefix)"\"                \
@@ -20,7 +17,7 @@ tidy_built_sources = \
 
 BUILT_SOURCES += $(tidy_built_sources)
 
-STAMP_FILES = stamp-tidy-marshal.h stamp-tidy-enum-types.h
+TIDY_STAMP_FILES = stamp-tidy-marshal.h stamp-tidy-enum-types.h
 
 # please, keep this sorted alphabetically
 tidy_source_h =                                        \
@@ -100,7 +97,7 @@ libtidy_1_0_la_SOURCES = \
 libtidy_1_0_la_CPPFLAGS = $(tidy_cflags)
 libtidy_1_0_la_LDFLAGS = $(LDADD)
 
-CLEANFILES += $(STAMP_FILES) $(BUILT_SOURCES)
+CLEANFILES += $(TIDY_STAMP_FILES) $(BUILT_SOURCES)
 
 EXTRA_DIST =                                   \
        tidy/tidy-enum-types.h.in               \
diff --git a/src/Makefile-tray.am b/src/Makefile-tray.am
new file mode 100644 (file)
index 0000000..647026a
--- /dev/null
@@ -0,0 +1,57 @@
+tray_cflags =                                  \
+       -I$(top_srcdir)/src                     \
+       -DG_DISABLE_DEPRECATED                  \
+       -DG_LOG_DOMAIN=\"notification_area\"    \
+       $(TRAY_CFLAGS)                          \
+       $(NULL)
+
+tray_built_sources = \
+       na-marshal.h            \
+       na-marshal.c
+
+BUILT_SOURCES += $(tray_built_sources)
+
+TRAY_STAMP_FILES = stamp-na-marshal.h
+
+# please, keep this sorted alphabetically
+tray_source =                  \
+       tray/na-tray-child.c    \
+       tray/na-tray-child.h    \
+       tray/na-tray-manager.c  \
+       tray/na-tray-manager.h  \
+       $(NULL)
+
+na-marshal.h: stamp-na-marshal.h
+       @true
+stamp-na-marshal.h: Makefile tray/na-marshal.list
+       $(GLIB_GENMARSHAL) \
+               --prefix=_na_marshal \
+               --header \
+       $(srcdir)/tray/na-marshal.list > xgen-tmh && \
+       (cmp -s xgen-tmh na-marshal.h || cp -f xgen-tmh na-marshal.h) && \
+       rm -f xgen-tmh && \
+       echo timestamp > $(@F)
+
+na-marshal.c: Makefile tray/na-marshal.list
+       (echo "#include \"na-marshal.h\"" ; \
+        $(GLIB_GENMARSHAL) \
+               --prefix=_na_marshal \
+               --body \
+        $(srcdir)/tray/na-marshal.list ) > xgen-tmc && \
+       cp -f xgen-tmc na-marshal.c && \
+       rm -f xgen-tmc
+
+lib_LTLIBRARIES += libtray.la
+
+libtray_la_LIBADD = $(TRAY_LIBS)
+libtray_la_SOURCES =   \
+       $(tray_source)          \
+       $(tray_built_sources)   \
+       $(NULL)
+libtray_la_CPPFLAGS = $(tray_cflags)
+libtray_la_LDFLAGS = $(LDADD)
+
+CLEANFILES += $(TRAY_STAMP_FILES) $(BUILT_SOURCES)
+
+EXTRA_DIST +=                                  \
+       tray/na-marshal.list
index 12859df41990da17ca99cd4be60dcc3e7d2d4fe7..df66f7349f00587aadc0f2d1453668b8e9071647 100644 (file)
@@ -2,9 +2,11 @@ BUILT_SOURCES =
 CLEANFILES =
 
 include Makefile-tidy.am
+include Makefile-tray.am
 
 gnome_shell_cflags =                           \
        $(MUTTER_PLUGIN_CFLAGS)                 \
+       -Itray                                  \
        -DGETTEXT_PACKAGE=gnome-shell           \
        -DJSDIR=\"$(pkgdatadir)/js\"
 
@@ -16,10 +18,15 @@ libgnome_shell_la_SOURCES =                 \
        shell-process.c                         \
        shell-process.h                         \
        shell-global.c                          \
-       shell-global.h
+       shell-global.h                          \
+       shell-tray-manager.c                    \
+       shell-tray-manager.h
 
 libgnome_shell_la_LDFLAGS = -avoid-version -module
-libgnome_shell_la_LIBADD = $(MUTTER_PLUGIN_LIBS) libtidy-1.0.la
+libgnome_shell_la_LIBADD =     \
+       $(MUTTER_PLUGIN_LIBS)   \
+       libtidy-1.0.la          \
+       libtray.la
 libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
 
 # We can't have any undefined symbols when g-ir-scanner dlopens the library
@@ -33,7 +40,10 @@ libgnome_shell_introspect_la_SOURCES =               \
 # The dummy -rpath here is needed to convince libtool to build a
 # noinst_LTLIBRARY shared
 libgnome_shell_introspect_la_LDFLAGS = -avoid-version -module -rpath $(libdir)
-libgnome_shell_introspect_la_LIBADD = $(MUTTER_PLUGIN_LIBS) libtidy-1.0.la
+libgnome_shell_introspect_la_LIBADD =  \
+       $(MUTTER_PLUGIN_LIBS)           \
+       libtidy-1.0.la                  \
+       libtray.la
 libgnome_shell_introspect_la_CPPFLAGS = $(gnome_shell_cflags)
 
 typelibdir = $(pkglibdir)/girepository
@@ -49,7 +59,7 @@ Shell-0.1.gir: libgnome-shell-introspect.la $(libgnome_shell_la_SOURCES) Makefil
                --include=Clutter-0.8                   \
                --include=Meta-2.25                     \
                --library=gnome-shell-introspect        \
--              $(libgnome_shell_la_SOURCES)            \
+               $(libgnome_shell_la_SOURCES)            \
                $(libgnome_shell_la_CPPFLAGS)           \
                -o $@.tmp
        sed 's/gnome-shell-introspect/gnome-shell/' < $@.tmp > $@ && rm $@.tmp
diff --git a/src/shell-tray-manager.c b/src/shell-tray-manager.c
new file mode 100644 (file)
index 0000000..69725f9
--- /dev/null
@@ -0,0 +1,215 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <clutter/clutter.h>
+#include <clutter/glx/clutter-glx.h>
+#include <clutter/x11/clutter-x11.h>
+#include <gtk/gtk.h>
+
+#include "shell-tray-manager.h"
+#include "na-tray-manager.h"
+
+struct _ShellTrayManagerPrivate {
+  NaTrayManager *na_manager;
+  ClutterStage *stage;
+  GdkWindow *stage_window;
+
+  GHashTable *icons;
+};
+
+typedef struct {
+  ShellTrayManager *manager;
+  GtkWidget *socket;
+  GtkWidget *window;
+  ClutterActor *actor;
+} ShellTrayManagerChild;
+
+/* Signals */
+enum
+{
+  TRAY_ICON_ADDED,
+  TRAY_ICON_REMOVED,
+  LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (ShellTrayManager, shell_tray_manager, G_TYPE_OBJECT);
+
+static guint shell_tray_manager_signals [LAST_SIGNAL] = { 0 };
+
+static void na_tray_icon_added (NaTrayManager *na_manager, GtkWidget *child, gpointer manager);
+static void na_tray_icon_removed (NaTrayManager *na_manager, GtkWidget *child, gpointer manager);
+
+static void
+free_tray_icon (gpointer data)
+{
+  ShellTrayManagerChild *child = data;
+
+  gtk_widget_hide (child->window);
+  gtk_widget_destroy (child->window);
+  g_signal_handlers_disconnect_matched (child->actor, G_SIGNAL_MATCH_DATA,
+                                        0, 0, NULL, NULL, child);
+  g_object_unref (child->actor);
+  g_slice_free (ShellTrayManagerChild, child);
+}
+
+static void
+shell_tray_manager_init (ShellTrayManager *manager)
+{
+  manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, SHELL_TYPE_TRAY_MANAGER,
+                                               ShellTrayManagerPrivate);
+  manager->priv->na_manager = na_tray_manager_new ();
+
+  manager->priv->icons = g_hash_table_new_full (NULL, NULL,
+                                                NULL, free_tray_icon);
+
+  g_signal_connect (manager->priv->na_manager, "tray-icon-added",
+                    G_CALLBACK (na_tray_icon_added), manager);
+  g_signal_connect (manager->priv->na_manager, "tray-icon-removed",
+                    G_CALLBACK (na_tray_icon_removed), manager);
+}
+
+static void
+shell_tray_manager_finalize (GObject *object)
+{
+  ShellTrayManager *manager = SHELL_TRAY_MANAGER (object);
+
+  g_object_unref (manager->priv->na_manager);
+  g_object_unref (manager->priv->stage);
+  g_object_unref (manager->priv->stage_window);
+  g_hash_table_destroy (manager->priv->icons);
+
+  G_OBJECT_CLASS (shell_tray_manager_parent_class)->finalize (object);
+}
+
+static void
+shell_tray_manager_class_init (ShellTrayManagerClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (ShellTrayManagerPrivate));
+
+  gobject_class->finalize = shell_tray_manager_finalize;
+
+  shell_tray_manager_signals[TRAY_ICON_ADDED] =
+    g_signal_new ("tray-icon-added",
+                 G_TYPE_FROM_CLASS (klass),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (ShellTrayManagerClass, tray_icon_added),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__OBJECT,
+                 G_TYPE_NONE, 1,
+                  CLUTTER_TYPE_ACTOR);
+  shell_tray_manager_signals[TRAY_ICON_REMOVED] =
+    g_signal_new ("tray-icon-removed",
+                 G_TYPE_FROM_CLASS (klass),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (ShellTrayManagerClass, tray_icon_removed),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__OBJECT,
+                 G_TYPE_NONE, 1,
+                  CLUTTER_TYPE_ACTOR);
+}
+
+ShellTrayManager *
+shell_tray_manager_new (void)
+{
+  return g_object_new (SHELL_TYPE_TRAY_MANAGER, NULL);
+}
+
+void
+shell_tray_manager_manage_stage (ShellTrayManager *manager,
+                                 ClutterStage     *stage)
+{
+  Window stage_xwin;
+
+  g_return_if_fail (manager->priv->stage == NULL);
+
+  manager->priv->stage = g_object_ref (stage);
+  stage_xwin = clutter_x11_get_stage_window (stage);
+  manager->priv->stage_window = gdk_window_lookup (stage_xwin);
+  if (manager->priv->stage_window)
+    g_object_ref (manager->priv->stage_window);
+  else
+    manager->priv->stage_window = gdk_window_foreign_new (stage_xwin);
+
+  na_tray_manager_manage_screen (manager->priv->na_manager,
+                                 gdk_drawable_get_screen (GDK_DRAWABLE (manager->priv->stage_window)));
+}
+
+static void
+actor_moved (GObject *object, GParamSpec *param, gpointer user_data)
+{
+  ShellTrayManagerChild *child = user_data;
+  ClutterActor *actor = child->actor;
+  int wx = 0, wy = 0, x, y, ax, ay;
+
+  /* Find the actor's new coordinates in terms of the stage (which is
+   * child->window's parent window.
+   */
+  while (actor)
+    {
+      clutter_actor_get_position (actor, &x, &y);
+      clutter_actor_get_anchor_point (actor, &ax, &ay);
+
+      wx += x - ax;
+      wy += y - ay;
+
+      actor = clutter_actor_get_parent (actor);
+    }
+
+  gtk_window_move (GTK_WINDOW (child->window), wx, wy);
+}
+
+static void
+na_tray_icon_added (NaTrayManager *na_manager, GtkWidget *socket,
+                    gpointer user_data)
+{
+  ShellTrayManager *manager = user_data;
+  GtkWidget *win;
+  ClutterActor *icon;
+  ShellTrayManagerChild *child;
+
+  win = gtk_window_new (GTK_WINDOW_POPUP);
+  gtk_container_add (GTK_CONTAINER (win), socket);
+
+  gtk_widget_set_size_request (win, 24, 24);
+  gtk_widget_realize (win);
+
+  gtk_widget_set_parent_window (win, manager->priv->stage_window);
+  gdk_window_reparent (win->window, manager->priv->stage_window, 0, 0);
+  gtk_widget_show_all (win);
+
+  icon = clutter_glx_texture_pixmap_new_with_window (GDK_WINDOW_XWINDOW (win->window));
+  clutter_x11_texture_pixmap_set_automatic (CLUTTER_X11_TEXTURE_PIXMAP (icon), TRUE);
+  clutter_actor_set_size (icon, 24, 24);
+
+  child = g_slice_new (ShellTrayManagerChild);
+  child->window = win;
+  child->socket = socket;
+  child->actor = g_object_ref (icon);
+  g_hash_table_insert (manager->priv->icons, socket, child);
+
+  g_signal_connect (child->actor, "notify::x",
+                    G_CALLBACK (actor_moved), child);
+  g_signal_connect (child->actor, "notify::y",
+                    G_CALLBACK (actor_moved), child);
+
+  g_signal_emit (manager,
+                 shell_tray_manager_signals[TRAY_ICON_ADDED], 0,
+                 icon);
+}
+
+static void
+na_tray_icon_removed (NaTrayManager *na_manager, GtkWidget *socket,
+                      gpointer user_data)
+{
+  ShellTrayManager *manager = user_data;
+  ShellTrayManagerChild *child;
+
+  child = g_hash_table_lookup (manager->priv->icons, socket);
+  g_return_if_fail (child != NULL);
+
+  g_signal_emit (manager,
+                 shell_tray_manager_signals[TRAY_ICON_REMOVED], 0,
+                 child->actor);
+  g_hash_table_remove (manager->priv->icons, socket);
+}
diff --git a/src/shell-tray-manager.h b/src/shell-tray-manager.h
new file mode 100644 (file)
index 0000000..7df38bb
--- /dev/null
@@ -0,0 +1,47 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef __SHELL_TRAY_MANAGER_H__
+#define __SHELL_TRAY_MANAGER_H__
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_TRAY_MANAGER                        (shell_tray_manager_get_type ())
+#define SHELL_TRAY_MANAGER(obj)                        (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_TRAY_MANAGER, ShellTrayManager))
+#define SHELL_TRAY_MANAGER_CLASS(klass)                (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_TRAY_MANAGER, ShellTrayManagerClass))
+#define SHELL_IS_TRAY_MANAGER(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_TRAY_MANAGER))
+#define SHELL_IS_TRAY_MANAGER_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_TRAY_MANAGER))
+#define SHELL_TRAY_MANAGER_GET_CLASS(obj)      (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_TRAY_MANAGER, ShellTrayManagerClass))
+       
+typedef struct _ShellTrayManager        ShellTrayManager;
+typedef struct _ShellTrayManagerPrivate ShellTrayManagerPrivate;
+typedef struct _ShellTrayManagerClass   ShellTrayManagerClass;
+
+struct _ShellTrayManager
+{
+  GObject parent_instance;
+
+  ShellTrayManagerPrivate *priv;
+};
+
+struct _ShellTrayManagerClass
+{
+  GObjectClass parent_class;
+
+  void (* tray_icon_added)   (ShellTrayManager *manager,
+                             ClutterActor     *icon); 
+  void (* tray_icon_removed) (ShellTrayManager *manager,
+                             ClutterActor     *icon);
+
+};
+
+GType             shell_tray_manager_get_type     (void);
+
+ShellTrayManager *shell_tray_manager_new          (void);
+void              shell_tray_manager_manage_stage (ShellTrayManager *manager,
+                                                   ClutterStage     *stage);
+
+G_END_DECLS
+
+#endif /* __SHELL_TRAY_MANAGER_H__ */
diff --git a/src/tray/na-marshal.list b/src/tray/na-marshal.list
new file mode 100644 (file)
index 0000000..e3fc399
--- /dev/null
@@ -0,0 +1,3 @@
+VOID:OBJECT,OBJECT
+VOID:OBJECT,STRING,LONG,LONG
+VOID:OBJECT,LONG
diff --git a/src/tray/na-tray-child.c b/src/tray/na-tray-child.c
new file mode 100644 (file)
index 0000000..4c17834
--- /dev/null
@@ -0,0 +1,386 @@
+/* na-tray-child.c
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "na-tray-child.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+
+G_DEFINE_TYPE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET)
+
+static void
+na_tray_child_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (na_tray_child_parent_class)->finalize (object);
+}
+
+static void
+na_tray_child_realize (GtkWidget *widget)
+{
+  NaTrayChild *child = NA_TRAY_CHILD (widget);
+  GdkVisual *visual = gtk_widget_get_visual (widget);
+  gboolean visual_has_alpha;
+
+  GTK_WIDGET_CLASS (na_tray_child_parent_class)->realize (widget);
+
+  /* We have alpha if the visual has something other than red, green, and blue */
+  visual_has_alpha = visual->red_prec + visual->blue_prec + visual->green_prec < visual->depth;
+
+  if (visual_has_alpha && gdk_display_supports_composite (gtk_widget_get_display (widget)))
+    {
+      /* We have real transparency with an ARGB visual and the Composite extension.
+       */
+
+      /* Set a transparent background */
+      GdkColor transparent = { 0, 0, 0, 0 }; /* only pixel=0 matters */
+      gdk_window_set_background(widget->window, &transparent);
+      gdk_window_set_composited (widget->window, TRUE);
+
+      child->is_composited = TRUE;
+      child->parent_relative_bg = FALSE;
+    }
+  else if (visual == gdk_window_get_visual (gdk_window_get_parent (widget->window)))
+    {
+      /* Otherwise, if the visual matches the visual of the parent window, we can
+       * use a parent-relative background and fake transparency.
+       */
+      gdk_window_set_back_pixmap (widget->window, NULL, TRUE);
+
+      child->is_composited = FALSE;
+      child->parent_relative_bg = TRUE;
+    }
+  else
+    {
+      /* Nothing to do; the icon will sit on top of an ugly gray box */
+
+      child->is_composited = FALSE;
+      child->parent_relative_bg = FALSE;
+    }
+
+  gtk_widget_set_app_paintable (GTK_WIDGET (child),
+                               child->parent_relative_bg || child->is_composited);
+
+  /* Double-buffering will interfere with the parent-relative-background fake
+   * transparency, since the double-buffer code doesn't know how to fill in the
+   * background of the double-buffer correctly.
+   */
+  gtk_widget_set_double_buffered (GTK_WIDGET (child), child->parent_relative_bg);
+}
+
+static void
+na_tray_child_style_set (GtkWidget *widget,
+                        GtkStyle  *previous_style)
+{
+  /* The default handler resets the background according to the new
+   * style.  We either use a transparent background or a parent-relative background
+   * and ignore the style background. So, just don't chain up.
+   */
+}
+
+#if 0
+/* This is adapted from code that was commented out in na-tray-manager.c; the code
+ * in na-tray-manager.c wouldn't have worked reliably, this will. So maybe it can
+ * be reenabled. On other hand, things seem to be working fine without it.
+ *
+ * If reenabling, you need to hook it up in na_tray_child_class_init().
+ */
+static void
+na_tray_child_size_request (GtkWidget      *widget,
+                           GtkRequisition *request)
+{
+  GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_request (widget, request);
+
+  /*
+   * Make sure the icons have a meaningful size ..
+   */ 
+  if ((request->width < 16) || (request->height < 16))
+    {
+      gint nw = MAX (24, request->width);
+      gint nh = MAX (24, request->height);
+      g_warning (_("tray icon has requested a size of (%i x %i), resizing to (%i x %i)"), 
+                req.width, req.height, nw, nh);
+      request->width = nw;
+      request->height = nh;
+    }
+}
+#endif
+
+static void
+na_tray_child_size_allocate (GtkWidget      *widget,
+                            GtkAllocation  *allocation)
+{
+  NaTrayChild *child = NA_TRAY_CHILD (widget);
+
+  gboolean moved = allocation->x != widget->allocation.x || allocation->y != widget->allocation.y;
+  gboolean resized = allocation->width != widget->allocation.width || allocation->height != widget->allocation.height;
+
+  /* When we are allocating the widget while mapped we need special handling for
+   * both real and fake transparency.
+   *
+   *  Real transparency: we need to invalidate and trigger a redraw of the old
+   *   and new areas. (GDK really should handle this for us, but doesn't as of
+   *   GTK+-2.14)
+   *
+   * Fake transparency: if the widget moved, we need to force the contents to be
+   *   redrawn with the new offset for the parent-relative background.
+   */
+  if ((moved || resized) && GTK_WIDGET_MAPPED (widget))
+    {
+      if (na_tray_child_is_composited (child))
+       gdk_window_invalidate_rect (gdk_window_get_parent (widget->window),
+                                   &widget->allocation, FALSE);
+    }
+
+  GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_allocate (widget, allocation);
+
+  if ((moved || resized) && GTK_WIDGET_MAPPED (widget))
+    {
+      if (na_tray_child_is_composited (NA_TRAY_CHILD (widget)))
+       gdk_window_invalidate_rect (gdk_window_get_parent (widget->window),
+                                   &widget->allocation, FALSE);
+      else if (moved && child->parent_relative_bg)
+       na_tray_child_force_redraw (child);
+    }
+}
+
+/* The plug window should completely occupy the area of the child, so we won't
+ * get an expose event. But in case we do (the plug unmaps itself, say), this
+ * expose handler draws with real or fake transparency.
+ */
+static gboolean
+na_tray_child_expose_event (GtkWidget      *widget,
+                           GdkEventExpose *event)
+{
+  NaTrayChild *child = NA_TRAY_CHILD (widget);
+
+  if (na_tray_child_is_composited (child))
+    {
+      /* Clear to transparent */
+      cairo_t *cr = gdk_cairo_create (widget->window);
+      cairo_set_source_rgba (cr, 0, 0, 0, 0);
+      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+      gdk_cairo_region (cr, event->region);
+      cairo_fill (cr);
+      cairo_destroy (cr);
+    }
+  else if (child->parent_relative_bg)
+    {
+      /* Clear to parent-relative pixmap */
+      gdk_window_clear_area (widget->window,
+                            event->area.x, event->area.y,
+                            event->area.width, event->area.height);
+    }
+
+  return FALSE;
+}
+
+static void
+na_tray_child_init (NaTrayChild *child)
+{
+}
+
+static void
+na_tray_child_class_init (NaTrayChildClass *klass)
+{
+  GObjectClass *gobject_class;
+  GtkWidgetClass *widget_class;
+
+  gobject_class = (GObjectClass *)klass;
+  widget_class = (GtkWidgetClass *)klass;
+
+  gobject_class->finalize = na_tray_child_finalize;
+  widget_class->style_set = na_tray_child_style_set;
+  widget_class->realize = na_tray_child_realize;
+  widget_class->size_allocate = na_tray_child_size_allocate;
+  widget_class->expose_event = na_tray_child_expose_event;
+}
+
+GtkWidget *
+na_tray_child_new (GdkScreen *screen,
+                  Window     icon_window)
+{
+  XWindowAttributes window_attributes;
+  Display *xdisplay;
+  NaTrayChild *child;
+  GdkVisual *visual;
+  GdkColormap *colormap;
+  gboolean new_colormap;
+  int result;
+
+  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+  g_return_val_if_fail (icon_window != None, NULL);
+
+  xdisplay = GDK_SCREEN_XDISPLAY (screen);
+
+  /* We need to determine the visual of the window we are embedding and create
+   * the socket in the same visual.
+   */
+
+  gdk_error_trap_push ();
+  result = XGetWindowAttributes (xdisplay, icon_window,
+                                &window_attributes);
+  gdk_error_trap_pop ();
+
+  if (!result) /* Window already gone */
+    return NULL;
+
+  visual = gdk_x11_screen_lookup_visual (screen,
+                                        window_attributes.visual->visualid);
+  if (!visual) /* Icon window is on another screen? */
+    return NULL;
+
+  new_colormap = FALSE;
+
+  if (visual == gdk_screen_get_rgb_visual (screen))
+    colormap = gdk_screen_get_rgb_colormap (screen);
+  else if (visual == gdk_screen_get_rgba_visual (screen))
+    colormap = gdk_screen_get_rgba_colormap (screen);
+  else if (visual == gdk_screen_get_system_visual (screen))
+    colormap = gdk_screen_get_system_colormap (screen);
+  else
+    {
+      colormap = gdk_colormap_new (visual, FALSE);
+      new_colormap = TRUE;
+    }
+
+  child = g_object_new (NA_TYPE_TRAY_CHILD, NULL);
+  child->icon_window = icon_window;
+
+  gtk_widget_set_colormap (GTK_WIDGET (child), colormap);
+
+  if (new_colormap)
+    g_object_unref (colormap);
+
+  return GTK_WIDGET (child);
+}
+
+char *
+na_tray_child_get_title (NaTrayChild *child)
+{
+  char *retval = NULL;
+  GdkDisplay *display;
+  Atom utf8_string, atom, type;
+  int result;
+  int format;
+  gulong nitems;
+  gulong bytes_after;
+  gchar *val;
+
+  g_return_val_if_fail (NA_IS_TRAY_CHILD (child), NULL);
+
+  display = gtk_widget_get_display (GTK_WIDGET (child));
+
+  utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING");
+  atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME");
+
+  gdk_error_trap_push ();
+
+  result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display),
+                              child->icon_window,
+                              atom,
+                              0, G_MAXLONG,
+                              False, utf8_string,
+                              &type, &format, &nitems,
+                              &bytes_after, (guchar **)&val);
+  
+  if (gdk_error_trap_pop () || result != Success)
+    return NULL;
+
+  if (type != utf8_string ||
+      format != 8 ||
+      nitems == 0)
+    {
+      if (val)
+       XFree (val);
+      return NULL;
+    }
+
+  if (!g_utf8_validate (val, nitems, NULL))
+    {
+      XFree (val);
+      return NULL;
+    }
+
+  retval = g_strndup (val, nitems);
+
+  XFree (val);
+
+  return retval;
+}
+
+gboolean
+na_tray_child_is_composited (NaTrayChild *child)
+{
+  g_return_val_if_fail (NA_IS_TRAY_CHILD (child), FALSE);
+
+  return child->is_composited;
+}
+
+/* If we are faking transparency with a window-relative background, force a
+ * redraw of the icon. This should be called if the background changes or if
+ * the child is shifed with respect to the background.
+ */
+void
+na_tray_child_force_redraw (NaTrayChild *child)
+{
+  GtkWidget *widget = GTK_WIDGET (child);
+
+  if (GTK_WIDGET_MAPPED (child) && child->parent_relative_bg)
+    {
+#if 1
+      /* Sending an ExposeEvent might cause redraw problems if the
+       * icon is expecting the server to clear-to-background before
+       * the redraw. It should be ok for GtkStatusIcon or EggTrayIcon.
+       */
+      Display *xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget));
+      XEvent xev;
+
+      xev.xexpose.type = Expose;
+      xev.xexpose.window = GDK_WINDOW_XWINDOW (GTK_SOCKET (child)->plug_window);
+      xev.xexpose.x = 0;
+      xev.xexpose.y = 0;
+      xev.xexpose.width = widget->allocation.width;
+      xev.xexpose.height = widget->allocation.height;
+      xev.xexpose.count = 0;
+
+      gdk_error_trap_push ();
+      XSendEvent (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)),
+                 xev.xexpose.window,
+                 False, ExposureMask,
+                 &xev);
+      /* We have to sync to reliably catch errors from the XSendEvent(),
+       * since that is asynchronous.
+       */
+      XSync (xdisplay, False);
+      gdk_error_trap_pop ();
+#else
+      /* Hiding and showing is the safe way to do it, but can result in more
+       * flickering.
+       */
+      gdk_window_hide (widget->window);
+      gdk_window_show (widget->window);
+#endif
+    }
+}
diff --git a/src/tray/na-tray-child.h b/src/tray/na-tray-child.h
new file mode 100644 (file)
index 0000000..2483054
--- /dev/null
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* na-tray-child.h
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __NA_TRAY_CHILD_H__
+#define __NA_TRAY_CHILD_H__
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_TRAY_CHILD                     (na_tray_child_get_type ())
+#define NA_TRAY_CHILD(obj)                     (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_CHILD, NaTrayChild))
+#define NA_TRAY_CHILD_CLASS(klass)             (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_CHILD, NaTrayChildClass))
+#define NA_IS_TRAY_CHILD(obj)                  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_CHILD))
+#define NA_IS_TRAY_CHILD_CLASS(klass)          (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_CHILD))
+#define NA_TRAY_CHILD_GET_CLASS(obj)           (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_CHILD, NaTrayChildClass))
+
+typedef struct _NaTrayChild        NaTrayChild;
+typedef struct _NaTrayChildClass  NaTrayChildClass;
+typedef struct _NaTrayChildChild  NaTrayChildChild;
+
+struct _NaTrayChild
+{
+  GtkSocket parent_instance;
+  Window icon_window;
+  guint is_composited : 1;
+  guint parent_relative_bg : 1;
+};
+
+struct _NaTrayChildClass
+{
+  GtkSocketClass parent_class;
+};
+
+GType           na_tray_child_get_type        (void);
+
+GtkWidget      *na_tray_child_new           (GdkScreen   *screen,
+                                            Window       icon_window);
+char           *na_tray_child_get_title     (NaTrayChild *child);
+gboolean        na_tray_child_is_composited (NaTrayChild *child);
+void            na_tray_child_force_redraw  (NaTrayChild *child);
+
+G_END_DECLS
+
+#endif /* __NA_TRAY_CHILD_H__ */
diff --git a/src/tray/na-tray-manager.c b/src/tray/na-tray-manager.c
new file mode 100644 (file)
index 0000000..34af8c7
--- /dev/null
@@ -0,0 +1,842 @@
+/* na-tray-manager.c
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Used to be: eggtraymanager.c
+ */
+
+#include <config.h>
+#include <string.h>
+#include <libintl.h>
+
+#include "na-tray-manager.h"
+
+#include <gdkconfig.h>
+#include <glib/gi18n.h>
+#if defined (GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#elif defined (GDK_WINDOWING_WIN32)
+#include <gdk/gdkwin32.h>
+#endif
+#include <gtk/gtkinvisible.h>
+#include <gtk/gtksocket.h>
+#include <gtk/gtkwindow.h>
+
+#include "na-marshal.h"
+
+/* Signals */
+enum
+{
+  TRAY_ICON_ADDED,
+  TRAY_ICON_REMOVED,
+  MESSAGE_SENT,
+  MESSAGE_CANCELLED,
+  LOST_SELECTION,
+  LAST_SIGNAL
+};
+
+enum {
+  PROP_0,
+  PROP_ORIENTATION
+};
+
+typedef struct
+{
+  long id, len;
+  long remaining_len;
+  
+  long timeout;
+  char *str;
+#ifdef GDK_WINDOWING_X11
+  Window window;
+#endif
+} PendingMessage;
+
+static guint manager_signals[LAST_SIGNAL];
+
+#define SYSTEM_TRAY_REQUEST_DOCK    0
+#define SYSTEM_TRAY_BEGIN_MESSAGE   1
+#define SYSTEM_TRAY_CANCEL_MESSAGE  2
+
+#define SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define SYSTEM_TRAY_ORIENTATION_VERT 1
+
+#ifdef GDK_WINDOWING_X11
+static gboolean na_tray_manager_check_running_screen_x11 (GdkScreen *screen);
+#endif
+
+static void na_tray_manager_finalize     (GObject      *object);
+static void na_tray_manager_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec);
+static void na_tray_manager_get_property (GObject      *object,
+                                         guint         prop_id,
+                                         GValue       *value,
+                                         GParamSpec   *pspec);
+
+static void na_tray_manager_unmanage (NaTrayManager *manager);
+
+G_DEFINE_TYPE (NaTrayManager, na_tray_manager, G_TYPE_OBJECT)
+
+static void
+na_tray_manager_init (NaTrayManager *manager)
+{
+  manager->invisible = NULL;
+  manager->socket_table = g_hash_table_new (NULL, NULL);
+}
+
+static void
+na_tray_manager_class_init (NaTrayManagerClass *klass)
+{
+  GObjectClass *gobject_class;
+  
+  gobject_class = (GObjectClass *)klass;
+
+  gobject_class->finalize = na_tray_manager_finalize;
+  gobject_class->set_property = na_tray_manager_set_property;
+  gobject_class->get_property = na_tray_manager_get_property;
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_ORIENTATION,
+                                  g_param_spec_enum ("orientation",
+                                                     "orientation",
+                                                     "orientation",
+                                                     GTK_TYPE_ORIENTATION,
+                                                     GTK_ORIENTATION_HORIZONTAL,
+                                                     G_PARAM_READWRITE |
+                                                     G_PARAM_CONSTRUCT |
+                                                     G_PARAM_STATIC_NAME |
+                                                     G_PARAM_STATIC_NICK |
+                                                     G_PARAM_STATIC_BLURB));
+  
+  manager_signals[TRAY_ICON_ADDED] =
+    g_signal_new ("tray_icon_added",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_added),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__OBJECT,
+                 G_TYPE_NONE, 1,
+                 GTK_TYPE_SOCKET);
+
+  manager_signals[TRAY_ICON_REMOVED] =
+    g_signal_new ("tray_icon_removed",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_removed),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__OBJECT,
+                 G_TYPE_NONE, 1,
+                 GTK_TYPE_SOCKET);
+  manager_signals[MESSAGE_SENT] =
+    g_signal_new ("message_sent",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (NaTrayManagerClass, message_sent),
+                 NULL, NULL,
+                 _na_marshal_VOID__OBJECT_STRING_LONG_LONG,
+                 G_TYPE_NONE, 4,
+                 GTK_TYPE_SOCKET,
+                 G_TYPE_STRING,
+                 G_TYPE_LONG,
+                 G_TYPE_LONG);
+  manager_signals[MESSAGE_CANCELLED] =
+    g_signal_new ("message_cancelled",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (NaTrayManagerClass, message_cancelled),
+                 NULL, NULL,
+                 _na_marshal_VOID__OBJECT_LONG,
+                 G_TYPE_NONE, 2,
+                 GTK_TYPE_SOCKET,
+                 G_TYPE_LONG);
+  manager_signals[LOST_SELECTION] =
+    g_signal_new ("lost_selection",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (NaTrayManagerClass, lost_selection),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0);
+
+#if defined (GDK_WINDOWING_X11)
+  /* Nothing */
+#elif defined (GDK_WINDOWING_WIN32)
+  g_warning ("Port NaTrayManager to Win32");
+#else
+  g_warning ("Port NaTrayManager to this GTK+ backend");
+#endif
+}
+
+static void
+na_tray_manager_finalize (GObject *object)
+{
+  NaTrayManager *manager;
+  
+  manager = NA_TRAY_MANAGER (object);
+
+  na_tray_manager_unmanage (manager);
+
+  g_list_free (manager->messages);
+  g_hash_table_destroy (manager->socket_table);
+  
+  G_OBJECT_CLASS (na_tray_manager_parent_class)->finalize (object);
+}
+
+static void
+na_tray_manager_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  NaTrayManager *manager = NA_TRAY_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      na_tray_manager_set_orientation (manager, g_value_get_enum (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+na_tray_manager_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  NaTrayManager *manager = NA_TRAY_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, manager->orientation);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+NaTrayManager *
+na_tray_manager_new (void)
+{
+  NaTrayManager *manager;
+
+  manager = g_object_new (NA_TYPE_TRAY_MANAGER, NULL);
+
+  return manager;
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static gboolean
+na_tray_manager_plug_removed (GtkSocket       *socket,
+                             NaTrayManager   *manager)
+{
+  NaTrayChild *child = NA_TRAY_CHILD (socket);
+
+  g_hash_table_remove (manager->socket_table, GINT_TO_POINTER (child->icon_window));
+  g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child);
+
+  /* This destroys the socket. */
+  return FALSE;
+}
+
+static void
+na_tray_manager_handle_dock_request (NaTrayManager       *manager,
+                                    XClientMessageEvent *xevent)
+{
+  Window icon_window = xevent->data.l[2];
+  GtkWidget *child;
+
+  if (g_hash_table_lookup (manager->socket_table, GINT_TO_POINTER (icon_window)))
+    {
+      /* We already got this notification earlier, ignore this one */
+      return;
+    }
+
+  child = na_tray_child_new (manager->screen, icon_window);
+  if (child == NULL) /* already gone or other error */
+    return;
+
+  g_signal_emit (manager, manager_signals[TRAY_ICON_ADDED], 0,
+                child);
+
+  /* If the child wasn't attached, then destroy it */
+
+  if (!GTK_IS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (child))))
+    {
+      gtk_widget_destroy (child);
+      return;
+    }
+
+  g_signal_connect (child, "plug_removed",
+                   G_CALLBACK (na_tray_manager_plug_removed), manager);
+
+  gtk_socket_add_id (GTK_SOCKET (child), icon_window);
+
+  if (!GTK_SOCKET (child)->plug_window)
+    {
+      /* Embedding failed, we won't get a plug-removed signal */
+      g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child);
+      gtk_widget_destroy (child);
+      return;
+    }
+
+  g_hash_table_insert (manager->socket_table, GINT_TO_POINTER (icon_window), child);
+  gtk_widget_show (child);
+}
+
+static void
+pending_message_free (PendingMessage *message)
+{
+  g_free (message->str);
+  g_free (message);
+}
+
+static GdkFilterReturn
+na_tray_manager_handle_client_message_message_data (GdkXEvent *xev,
+                                                    GdkEvent  *event,
+                                                    gpointer   data)
+{
+  XClientMessageEvent *xevent;
+  NaTrayManager       *manager;
+  GList               *p;
+  int                  len;
+  
+  xevent  = (XClientMessageEvent *) xev;
+  manager = data;
+
+  /* Try to see if we can find the pending message in the list */
+  for (p = manager->messages; p; p = p->next)
+    {
+      PendingMessage *msg = p->data;
+
+      if (xevent->window == msg->window)
+       {
+         /* Append the message */
+         len = MIN (msg->remaining_len, 20);
+
+         memcpy ((msg->str + msg->len - msg->remaining_len),
+                 &xevent->data, len);
+         msg->remaining_len -= len;
+
+         if (msg->remaining_len == 0)
+           {
+             GtkSocket *socket;
+
+             socket = g_hash_table_lookup (manager->socket_table,
+                                            GINT_TO_POINTER (msg->window));
+
+             if (socket)
+                 g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0,
+                                socket, msg->str, msg->id, msg->timeout);
+
+             pending_message_free (msg);
+             manager->messages = g_list_remove_link (manager->messages, p);
+              g_list_free_1 (p);
+           }
+
+          break;
+       }
+    }
+
+  return GDK_FILTER_REMOVE;
+}
+
+static void
+na_tray_manager_handle_begin_message (NaTrayManager       *manager,
+                                     XClientMessageEvent *xevent)
+{
+  GtkSocket      *socket;
+  GList          *p;
+  PendingMessage *msg;
+  long            timeout;
+  long            len;
+  long            id;
+
+  socket = g_hash_table_lookup (manager->socket_table,
+                                GINT_TO_POINTER (xevent->window));
+  /* we don't know about this tray icon, so ignore the message */
+  if (!socket)
+    return;
+
+  /* Check if the same message is already in the queue and remove it if so */
+  for (p = manager->messages; p; p = p->next)
+    {
+      PendingMessage *pmsg = p->data;
+
+      if (xevent->window == pmsg->window &&
+         xevent->data.l[4] == pmsg->id)
+       {
+         /* Hmm, we found it, now remove it */
+         pending_message_free (pmsg);
+         manager->messages = g_list_remove_link (manager->messages, p);
+          g_list_free_1 (p);
+         break;
+       }
+    }
+
+  timeout = xevent->data.l[2];
+  len     = xevent->data.l[3];
+  id      = xevent->data.l[4];
+
+  if (len == 0)
+    {
+      g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0,
+                     socket, "", id, timeout);
+    }
+  else
+    {
+      /* Now add the new message to the queue */
+      msg = g_new0 (PendingMessage, 1);
+      msg->window = xevent->window;
+      msg->timeout = timeout;
+      msg->len = len;
+      msg->id = id;
+      msg->remaining_len = msg->len;
+      msg->str = g_malloc (msg->len + 1);
+      msg->str[msg->len] = '\0';
+      manager->messages = g_list_prepend (manager->messages, msg);
+    }
+}
+
+static void
+na_tray_manager_handle_cancel_message (NaTrayManager       *manager,
+                                      XClientMessageEvent *xevent)
+{
+  GList     *p;
+  GtkSocket *socket;
+  
+  /* Check if the message is in the queue and remove it if so */
+  for (p = manager->messages; p; p = p->next)
+    {
+      PendingMessage *msg = p->data;
+
+      if (xevent->window == msg->window &&
+         xevent->data.l[4] == msg->id)
+       {
+         pending_message_free (msg);
+         manager->messages = g_list_remove_link (manager->messages, p);
+          g_list_free_1 (p);
+         break;
+       }
+    }
+
+  socket = g_hash_table_lookup (manager->socket_table,
+                                GINT_TO_POINTER (xevent->window));
+  
+  if (socket)
+    {
+      g_signal_emit (manager, manager_signals[MESSAGE_CANCELLED], 0,
+                    socket, xevent->data.l[2]);
+    }
+}
+
+static GdkFilterReturn
+na_tray_manager_handle_client_message_opcode (GdkXEvent *xev,
+                                              GdkEvent  *event,
+                                              gpointer   data)
+{
+  XClientMessageEvent *xevent;
+  NaTrayManager       *manager;
+
+  xevent  = (XClientMessageEvent *) xev;
+  manager = data;
+
+  switch (xevent->data.l[1])
+    {
+    case SYSTEM_TRAY_REQUEST_DOCK:
+      /* Ignore this one since we don't know on which window this was received
+       * and so we can't know for which screen this is. It will be handled
+       * in na_tray_manager_window_filter() since we also receive it there */
+      break;
+
+    case SYSTEM_TRAY_BEGIN_MESSAGE:
+      na_tray_manager_handle_begin_message (manager, xevent);
+      return GDK_FILTER_REMOVE;
+
+    case SYSTEM_TRAY_CANCEL_MESSAGE:
+      na_tray_manager_handle_cancel_message (manager, xevent);
+      return GDK_FILTER_REMOVE;
+    default:
+      break;
+    }
+
+  return GDK_FILTER_CONTINUE;
+}
+
+static GdkFilterReturn
+na_tray_manager_window_filter (GdkXEvent *xev,
+                               GdkEvent  *event,
+                               gpointer   data)
+{
+  XEvent        *xevent = (GdkXEvent *)xev;
+  NaTrayManager *manager = data;
+
+  if (xevent->type == ClientMessage)
+    {
+      /* We handle this client message here. See comment in
+       * na_tray_manager_handle_client_message_opcode() for details */
+      if (xevent->xclient.message_type == manager->opcode_atom &&
+          xevent->xclient.data.l[1]    == SYSTEM_TRAY_REQUEST_DOCK)
+       {
+          na_tray_manager_handle_dock_request (manager,
+                                               (XClientMessageEvent *) xevent);
+          return GDK_FILTER_REMOVE;
+       }
+    }
+  else if (xevent->type == SelectionClear)
+    {
+      g_signal_emit (manager, manager_signals[LOST_SELECTION], 0);
+      na_tray_manager_unmanage (manager);
+    }
+
+  return GDK_FILTER_CONTINUE;
+}
+
+#if 0
+//FIXME investigate why this doesn't work
+static gboolean
+na_tray_manager_selection_clear_event (GtkWidget         *widget,
+                                       GdkEventSelection *event,
+                                       NaTrayManager     *manager)
+{
+  g_signal_emit (manager, manager_signals[LOST_SELECTION], 0);
+  na_tray_manager_unmanage (manager);
+
+  return FALSE;
+}
+#endif
+#endif  
+
+static void
+na_tray_manager_unmanage (NaTrayManager *manager)
+{
+#ifdef GDK_WINDOWING_X11
+  GdkDisplay *display;
+  guint32     timestamp;
+  GtkWidget  *invisible;
+
+  if (manager->invisible == NULL)
+    return;
+
+  invisible = manager->invisible;
+  g_assert (GTK_IS_INVISIBLE (invisible));
+  g_assert (GTK_WIDGET_REALIZED (invisible));
+  g_assert (GDK_IS_WINDOW (invisible->window));
+  
+  display = gtk_widget_get_display (invisible);
+  
+  if (gdk_selection_owner_get_for_display (display, manager->selection_atom) ==
+      invisible->window)
+    {
+      timestamp = gdk_x11_get_server_time (invisible->window);      
+      gdk_selection_owner_set_for_display (display,
+                                           NULL,
+                                           manager->selection_atom,
+                                           timestamp,
+                                           TRUE);
+    }
+
+  //FIXME: we should also use gdk_remove_client_message_filter when it's
+  //available
+  // See bug #351254
+  gdk_window_remove_filter (invisible->window,
+                            na_tray_manager_window_filter, manager);  
+
+  manager->invisible = NULL; /* prior to destroy for reentrancy paranoia */
+  gtk_widget_destroy (invisible);
+  g_object_unref (G_OBJECT (invisible));
+#endif
+}
+
+static void
+na_tray_manager_set_orientation_property (NaTrayManager *manager)
+{
+#ifdef GDK_WINDOWING_X11
+  GdkDisplay *display;
+  Atom        orientation_atom;
+  gulong      data[1];
+
+  if (!manager->invisible || !manager->invisible->window)
+    return;
+
+  display = gtk_widget_get_display (manager->invisible);
+  orientation_atom = gdk_x11_get_xatom_by_name_for_display (display,
+                                                            "_NET_SYSTEM_TRAY_ORIENTATION");
+
+  data[0] = manager->orientation == GTK_ORIENTATION_HORIZONTAL ?
+               SYSTEM_TRAY_ORIENTATION_HORZ :
+               SYSTEM_TRAY_ORIENTATION_VERT;
+
+  XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+                  GDK_WINDOW_XWINDOW (manager->invisible->window),
+                   orientation_atom,
+                  XA_CARDINAL, 32,
+                  PropModeReplace,
+                  (guchar *) &data, 1);
+#endif
+}
+
+static void
+na_tray_manager_set_visual_property (NaTrayManager *manager)
+{
+#ifdef GDK_WINDOWING_X11
+  GdkDisplay *display;
+  Visual     *xvisual;
+  Atom        visual_atom;
+  gulong      data[1];
+
+  if (!manager->invisible || !manager->invisible->window)
+    return;
+
+  /* The visual property is a hint to the tray icons as to what visual they
+   * should use for their windows. If the X server has RGBA colormaps, then
+   * we tell the tray icons to use a RGBA colormap and we'll composite the
+   * icon onto its parents with real transparency. Otherwise, we just tell
+   * the icon to use our colormap, and we'll do some hacks with parent
+   * relative backgrounds to simulate transparency.
+   */
+
+  display = gtk_widget_get_display (manager->invisible);
+  visual_atom = gdk_x11_get_xatom_by_name_for_display (display,
+                                                      "_NET_SYSTEM_TRAY_VISUAL");
+
+  if (gdk_screen_get_rgba_visual (manager->screen) != NULL &&
+      gdk_display_supports_composite (display))
+    {
+      xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_rgba_visual (manager->screen));
+    }
+  else
+    {
+      /* We actually want the visual of the tray where the icons will
+       * be embedded. In almost all cases, this will be the same as the visual
+       * of the screen
+       */
+      GdkColormap *colormap = gdk_screen_get_default_colormap (manager->screen);
+      xvisual = GDK_VISUAL_XVISUAL (gdk_colormap_get_visual (colormap));
+    }
+
+  data[0] = XVisualIDFromVisual (xvisual);
+
+  XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+                  GDK_WINDOW_XWINDOW (manager->invisible->window),
+                   visual_atom,
+                  XA_VISUALID, 32,
+                  PropModeReplace,
+                  (guchar *) &data, 1);
+#endif
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static gboolean
+na_tray_manager_manage_screen_x11 (NaTrayManager *manager,
+                                  GdkScreen     *screen)
+{
+  GdkDisplay *display;
+  Screen     *xscreen;
+  GtkWidget  *invisible;
+  char       *selection_atom_name;
+  guint32     timestamp;
+  
+  g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), FALSE);
+  g_return_val_if_fail (manager->screen == NULL, FALSE);
+
+  /* If there's already a manager running on the screen
+   * we can't create another one.
+   */
+#if 0
+  if (na_tray_manager_check_running_screen_x11 (screen))
+    return FALSE;
+#endif
+  
+  manager->screen = screen;
+
+  display = gdk_screen_get_display (screen);
+  xscreen = GDK_SCREEN_XSCREEN (screen);
+  
+  invisible = gtk_invisible_new_for_screen (screen);
+  gtk_widget_realize (invisible);
+  
+  gtk_widget_add_events (invisible,
+                         GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK);
+
+  selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
+                                        gdk_screen_get_number (screen));
+  manager->selection_atom = gdk_atom_intern (selection_atom_name, FALSE);
+  g_free (selection_atom_name);
+
+  manager->invisible = invisible;
+  g_object_ref (G_OBJECT (manager->invisible));
+
+  na_tray_manager_set_orientation_property (manager);
+  na_tray_manager_set_visual_property (manager);
+  
+  timestamp = gdk_x11_get_server_time (invisible->window);
+
+  /* Check if we could set the selection owner successfully */
+  if (gdk_selection_owner_set_for_display (display,
+                                           invisible->window,
+                                           manager->selection_atom,
+                                           timestamp,
+                                           TRUE))
+    {
+      XClientMessageEvent xev;
+      GdkAtom             opcode_atom;
+      GdkAtom             message_data_atom;
+
+      xev.type = ClientMessage;
+      xev.window = RootWindowOfScreen (xscreen);
+      xev.message_type = gdk_x11_get_xatom_by_name_for_display (display,
+                                                                "MANAGER");
+
+      xev.format = 32;
+      xev.data.l[0] = timestamp;
+      xev.data.l[1] = gdk_x11_atom_to_xatom_for_display (display,
+                                                         manager->selection_atom);
+      xev.data.l[2] = GDK_WINDOW_XWINDOW (invisible->window);
+      xev.data.l[3] = 0;       /* manager specific data */
+      xev.data.l[4] = 0;       /* manager specific data */
+
+      XSendEvent (GDK_DISPLAY_XDISPLAY (display),
+                 RootWindowOfScreen (xscreen),
+                 False, StructureNotifyMask, (XEvent *)&xev);
+
+      opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE);
+      manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display,
+                                                                opcode_atom);
+
+      message_data_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA",
+                                           FALSE);
+
+      /* Add a window filter */
+#if 0
+      /* This is for when we lose the selection of _NET_SYSTEM_TRAY_Sx */
+      g_signal_connect (invisible, "selection-clear-event",
+                        G_CALLBACK (na_tray_manager_selection_clear_event),
+                        manager);
+#endif
+      /* This is for SYSTEM_TRAY_REQUEST_DOCK and SelectionClear */
+      gdk_window_add_filter (invisible->window,
+                             na_tray_manager_window_filter, manager);
+      /* This is for SYSTEM_TRAY_BEGIN_MESSAGE and SYSTEM_TRAY_CANCEL_MESSAGE */
+      gdk_display_add_client_message_filter (display, opcode_atom,
+                                             na_tray_manager_handle_client_message_opcode,
+                                             manager);
+      /* This is for _NET_SYSTEM_TRAY_MESSAGE_DATA */
+      gdk_display_add_client_message_filter (display, message_data_atom,
+                                             na_tray_manager_handle_client_message_message_data,
+                                             manager);
+      return TRUE;
+    }
+  else
+    {
+      gtk_widget_destroy (invisible);
+      g_object_unref (invisible);
+      manager->invisible = NULL;
+
+      manager->screen = NULL;
+ 
+      return FALSE;
+    }
+}
+
+#endif
+
+gboolean
+na_tray_manager_manage_screen (NaTrayManager *manager,
+                              GdkScreen     *screen)
+{
+  g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
+  g_return_val_if_fail (manager->screen == NULL, FALSE);
+
+#ifdef GDK_WINDOWING_X11
+  return na_tray_manager_manage_screen_x11 (manager, screen);
+#else
+  return FALSE;
+#endif
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static gboolean
+na_tray_manager_check_running_screen_x11 (GdkScreen *screen)
+{
+  GdkDisplay *display;
+  Atom        selection_atom;
+  char       *selection_atom_name;
+
+  display = gdk_screen_get_display (screen);
+  selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d",
+                                         gdk_screen_get_number (screen));
+  selection_atom = gdk_x11_get_xatom_by_name_for_display (display,
+                                                          selection_atom_name);
+  g_free (selection_atom_name);
+
+  if (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display),
+                          selection_atom) != None)
+    return TRUE;
+  else
+    return FALSE;
+}
+
+#endif
+
+gboolean
+na_tray_manager_check_running (GdkScreen *screen)
+{
+  g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE);
+
+#ifdef GDK_WINDOWING_X11
+  return na_tray_manager_check_running_screen_x11 (screen);
+#else
+  return FALSE;
+#endif
+}
+
+void
+na_tray_manager_set_orientation (NaTrayManager  *manager,
+                                GtkOrientation  orientation)
+{
+  g_return_if_fail (NA_IS_TRAY_MANAGER (manager));
+
+  if (manager->orientation != orientation)
+    {
+      manager->orientation = orientation;
+
+      na_tray_manager_set_orientation_property (manager);
+
+      g_object_notify (G_OBJECT (manager), "orientation");
+    }
+}
+
+GtkOrientation
+na_tray_manager_get_orientation (NaTrayManager *manager)
+{
+  g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), GTK_ORIENTATION_HORIZONTAL);
+
+  return manager->orientation;
+}
diff --git a/src/tray/na-tray-manager.h b/src/tray/na-tray-manager.h
new file mode 100644 (file)
index 0000000..5391403
--- /dev/null
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* na-tray-manager.h
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ * Copyright (C) 2003-2006 Vincent Untz
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Used to be: eggtraymanager.h
+ */
+
+#ifndef __NA_TRAY_MANAGER_H__
+#define __NA_TRAY_MANAGER_H__
+
+#include <gtk/gtkwidget.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "na-tray-child.h"
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_TRAY_MANAGER                   (na_tray_manager_get_type ())
+#define NA_TRAY_MANAGER(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManager))
+#define NA_TRAY_MANAGER_CLASS(klass)           (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass))
+#define NA_IS_TRAY_MANAGER(obj)                        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_MANAGER))
+#define NA_IS_TRAY_MANAGER_CLASS(klass)                (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_MANAGER))
+#define NA_TRAY_MANAGER_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass))
+       
+typedef struct _NaTrayManager      NaTrayManager;
+typedef struct _NaTrayManagerClass  NaTrayManagerClass;
+
+struct _NaTrayManager
+{
+  GObject parent_instance;
+
+#ifdef GDK_WINDOWING_X11
+  GdkAtom selection_atom;
+  Atom    opcode_atom;
+#endif
+  
+  GtkWidget *invisible;
+  GdkScreen *screen;
+  GtkOrientation orientation;
+
+  GList *messages;
+  GHashTable *socket_table;
+};
+
+struct _NaTrayManagerClass
+{
+  GObjectClass parent_class;
+
+  void (* tray_icon_added)   (NaTrayManager      *manager,
+                             NaTrayChild        *child);
+  void (* tray_icon_removed) (NaTrayManager      *manager,
+                             NaTrayChild        *child);
+
+  void (* message_sent)      (NaTrayManager      *manager,
+                             NaTrayChild        *child,
+                             const gchar        *message,
+                             glong               id,
+                             glong               timeout);
+  
+  void (* message_cancelled) (NaTrayManager      *manager,
+                             NaTrayChild        *child,
+                             glong               id);
+
+  void (* lost_selection)    (NaTrayManager      *manager);
+};
+
+GType           na_tray_manager_get_type        (void);
+
+gboolean        na_tray_manager_check_running   (GdkScreen          *screen);
+NaTrayManager  *na_tray_manager_new             (void);
+gboolean        na_tray_manager_manage_screen   (NaTrayManager      *manager,
+                                                GdkScreen          *screen);
+void            na_tray_manager_set_orientation (NaTrayManager      *manager,
+                                                GtkOrientation      orientation);
+GtkOrientation  na_tray_manager_get_orientation (NaTrayManager      *manager);
+
+G_END_DECLS
+
+#endif /* __NA_TRAY_MANAGER_H__ */